﻿using ArduinoAnalyzer.Tokens;
using ArduinoAnalyzer.Tokens.LexicalAnalysis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ArduinoAnalyzer.LexicalAnalysis
{
    public class LexicalAnalyzer
    {
        #region Constructors

        public LexicalAnalyzer()
        {
            this.InitializeStates();
        }

        #endregion

        #region Private Members

        private State _startState;

        #endregion

        #region Private Methods

        private void InitializeStates()
        {
            Func<char, bool> letter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
            Func<char, bool> digit = c => c >= '0' && c <= '9';
            Func<string, TokenType> reservedWord = this.GetReservedWordsFunc();

            var state0 = new State();
            var state1 = new State();
            var state2 = new State();
            var state3 = new State();
            var state4 = new State();
            var state5 = new State();
            var state6 = new State();
            var state7 = new State();
            var state8 = new State();
            var state9 = new State();
            var state12 = new State();
            var state13 = new State();
            var state14 = new State();
            var state15 = new State();
            var state16 = new State();
            var state17 = new State();
            var state18 = new State();
            var state19 = new State();
            var state20 = new State();
            var state21 = new State();
            var state22 = new State();
            var state23 = new State();
            var state24 = new State();
            var state25 = new State();
            var state26 = new State();
            var state27 = new State();
            var state28 = new State();
            var state29 = new State();
            var state30 = new State();
            var state31 = new State();
            var state32 = new State();
            var state34 = new State();
            var state35 = new State();
            var state36 = new State();
            var state37 = new State();
            var state38 = new State();
            var state39 = new State();
            var state40 = new State();
            var state41 = new State();
            var state42 = new State();
            var state43 = new State();
            var state44 = new State();
            var state45 = new State();
            var state46 = new State();
            var state47 = new State();
            var state48 = new State();
            var state49 = new State();
            var state50 = new State();
            var state51 = new State();
            var state52 = new State();
            var state53 = new State();
            var state54 = new State();
            var state55 = new State();
            var state56 = new State();
            var state57 = new State();

            state0.AddState(letter, state8)
                  .AddState('+', state1)
                  .AddState('-', state14)
                  .AddState('*', state17)
                  .AddState('/', state19)
                  .AddState('.', state2)
                  .AddState('=', state21)
                  .AddState('!', state23)
                  .AddState(digit, state4)
                  .AddState(';', state9)
                  .AddState('&', state25)
                  .AddState('|', state27)
                  .AddState('<', state29)
                  .AddState('>', state31)
                  .AddState('(', state34)
                  .AddState(')', state35)
                  .AddState('{', state36)
                  .AddState('}', state37)
                  .AddState('[', state38)
                  .AddState(']', state39)
                  .AddState('?', state42)
                  .AddState(':', state43)
                  .AddState(',', state44)
                  .AddState('%', state45)
                  .AddState('~', state47)
                  .AddState('^', state48);

            state1.SetToken(TokenType.Addition)
                  .AddState('.', state2)
                  .AddState('+', state12)
                  .AddState('=', state13);

            state2.AddState(digit, state3);

            state3.AddState(digit, state3)
                  .AddState('E', state5)
                  .SetToken(TokenType.RealConstant);

            state4.SetToken(TokenType.IntegerConstant)
                  .AddState(digit, state4)
                  .AddState('E', state5)
                  .AddState('.', state3);

            state5.AddState(digit, state7)
                  .AddState(c => c == '+' || c == '-', state6);

            state6.AddState(digit, state7);

            state7.AddState(digit, state7)
                  .SetToken(TokenType.RealConstant);

            state8.AddState(c => letter(c) || digit(c) || c == '_', state8)
                  .SetToken(reservedWord);

            state9.SetToken(TokenType.Semicolon);

            state12.SetToken(TokenType.Increment);

            state13.SetToken(TokenType.AdditionAssignment);

            state14.SetToken(TokenType.Subtraction)
                .AddState('.', state2)
                .AddState('-', state15)
                .AddState('=', state16);

            state15.SetToken(TokenType.Decrement);

            state16.SetToken(TokenType.SubtractionAssignment);

            state17.SetToken(TokenType.Multiplication)
                   .AddState('=', state18);

            state18.SetToken(TokenType.MultiplicationAssignment);

            state19.SetToken(TokenType.Division)
                   .AddState('=', state20)
                   .AddState('*', state54)
                   .AddState('/', state57);

            state20.SetToken(TokenType.DivisionAssignment);

            state21.SetToken(TokenType.Assignment)
                   .AddState('=', state22);

            state22.SetToken(TokenType.Equality);

            state23.AddState('=', state24)
                   .SetToken(TokenType.Not);

            state24.SetToken(TokenType.Inequality);

            state25.AddState('&', state26)
                   .AddState('=', state50)
                   .SetToken(TokenType.BitwiseAnd);

            state26.SetToken(TokenType.And);

            state27.AddState('|', state28)
                   .AddState('=', state51)
                   .SetToken(TokenType.BitwiseOr);

            state28.SetToken(TokenType.Or);

            state29.SetToken(TokenType.LessThan)
                   .AddState('=', state30)
                   .AddState('<', state40);

            state40.SetToken(TokenType.ShiftLeft)
                   .AddState('=', state52);

            state30.SetToken(TokenType.LessThanOrEqual);

            state31.SetToken(TokenType.GreaterThan)
                   .AddState('=', state32)
                   .AddState('>', state41);

            state41.SetToken(TokenType.ShiftRight)
                   .AddState('=', state53);

            state32.SetToken(TokenType.GreaterThanOrEqual);

            state34.SetToken(TokenType.OpenParentheses);

            state35.SetToken(TokenType.CloseParentheses);

            state36.SetToken(TokenType.OpenBraces);

            state37.SetToken(TokenType.CloseBraces);

            state38.SetToken(TokenType.OpenBrackets);

            state39.SetToken(TokenType.CloseBrackets);

            state42.SetToken(TokenType.QuestionMark);

            state43.SetToken(TokenType.Colon);

            state44.SetToken(TokenType.Comma);

            state45.SetToken(TokenType.DivisionRemainder)
                   .AddState('=', state46);

            state46.SetToken(TokenType.DivisionRemainderAssignment);

            state47.SetToken(TokenType.BitwiseNot);

            state48.SetToken(TokenType.BitwiseXor)
                   .AddState('=', state49);

            state49.SetToken(TokenType.BitwiseXorAssignment);

            state50.SetToken(TokenType.BitwiseAndAssignment);

            state51.SetToken(TokenType.BitwiseOrAssignment);

            state52.SetToken(TokenType.ShiftLeftAssignment);

            state53.SetToken(TokenType.ShiftRightAssignment);

            state54.AddState('*', state55)
                   .AddState(c => c != '\0', state54);

            state55.AddState('*', state55)
                   .AddState('/', state56)
                   .AddState(c => c != '\0', state54);

            state56.SetToken(TokenType.Comment);

            state57.AddState('\n', state56)
                   .AddState('\0', state56)
                   .AddState(c => c != '\0', state57);

            _startState = state0;
        }

        private Func<string, TokenType> GetReservedWordsFunc()
        {
            Func<string, TokenType> reservedWord = s =>
            {
                if (s == "while") return TokenType.While;
                if (s == "do") return TokenType.Do;
                if (s == "for") return TokenType.For;
                if (s == "if") return TokenType.If;
                if (s == "else") return TokenType.Else;
                if (s == "switch") return TokenType.Switch;
                if (s == "case") return TokenType.Case;
                if (s == "default") return TokenType.Default;
                if (s == "true") return TokenType.True;
                if (s == "void") return TokenType.Void;
                if (s == "false") return TokenType.False;
                //if (s == "HIGH") return TokenType.High;
                //if (s == "LOW") return TokenType.Low;
                //if (s == "INPUT") return TokenType.Input;
                //if (s == "OUTPUT") return TokenType.Output;
                if (s == "byte") return TokenType.Byte;
                if (s == "int") return TokenType.Int;
                if (s == "unsigned") return TokenType.Unsigned;
                if (s == "word") return TokenType.Word;
                if (s == "short") return TokenType.Short;
                if (s == "long") return TokenType.Long;
                if (s == "float") return TokenType.Float;
                if (s == "double") return TokenType.Double;
                if (s == "boolean") return TokenType.Boolean;
                if (s == "break") return TokenType.Break;
                if (s == "return") return TokenType.Return;
                if (s == "continue") return TokenType.Continue;
                if (s == "goto") return TokenType.GoTo;
                //if (s == "INPUT_PULLUP") return TokenType.InputPullup;
                if (s == "static") return TokenType.Static;
                if (s == "volatile") return TokenType.Volatile;
                if (s == "const") return TokenType.Const;
                //if (s == "pinMode") return TokenType.PinMode;
                //if (s == "digitalWrite") return TokenType.DigitalWrite;
                //if (s == "digitalRead") return TokenType.DigitalRead;
                //if (s == "analogReference") return TokenType.AnalogReference;
                //if (s == "analogRead") return TokenType.AnalogRead;
                //if (s == "analogWrite") return TokenType.AnalogWrite;
                //if (s == "analogReadResolution") return TokenType.AnalogReadResolution;
                //if (s == "analogWriteResolution") return TokenType.AnalogWriteResolution;
                //if (s == "tone") return TokenType.Tone;
                //if (s == "noTone") return TokenType.NoTone;
                //if (s == "shiftOut") return TokenType.ShiftOut;
                //if (s == "shiftIn") return TokenType.ShiftIn;
                //if (s == "pulseIn") return TokenType.PulseIn;
                //if (s == "millis") return TokenType.Millis;
                //if (s == "micros") return TokenType.Micros;
                //if (s == "delay") return TokenType.Delay;
                //if (s == "delayMicroseconds") return TokenType.DelayMicroseconds;
                //if (s == "min") return TokenType.Min;
                //if (s == "max") return TokenType.Max;
                //if (s == "abs") return TokenType.Abs;
                //if (s == "constrain") return TokenType.Constrain;
                //if (s == "map") return TokenType.Map;
                //if (s == "pow") return TokenType.Pow;
                //if (s == "sqrt") return TokenType.Sqrt;
                //if (s == "sin") return TokenType.Sin;
                //if (s == "cos") return TokenType.Cos;
                //if (s == "tan") return TokenType.Tan;
                //if (s == "randomSeed") return TokenType.RandomSeed;
                //if (s == "random") return TokenType.Random;
                //if (s == "lowByte") return TokenType.LowByte;
                //if (s == "highByte") return TokenType.HighByte;
                //if (s == "bitRead") return TokenType.BitRead;
                //if (s == "bitWrite") return TokenType.BitWrite;
                //if (s == "bitSet") return TokenType.BitSet;
                //if (s == "bitClear") return TokenType.BitClear;
                //if (s == "bit") return TokenType.Bit;
                //if (s == "attachInterrupt") return TokenType.AttachInterrupt;
                //if (s == "detachInterrupt") return TokenType.DetachInterrupt;
                //if (s == "interrupts") return TokenType.Interrupts;
                //if (s == "noInterrupts") return TokenType.NoInterrupts;
                //if (s == "sizeOf") return TokenType.SizeOf;
                return TokenType.Identifier;
            };
            return reservedWord;
        }

        private void PreProcessValidation(string text, ref int position)
        {
            Func<char, bool> end = c => c == ' ' || c == '\0' || c == '\t' || c == '\n';
            int tposition = position;
            int state;
            bool stop;
            do
            {
                state = 0;
                stop = false;
                while (!stop && tposition < text.Length)
                {
                    switch (state)
                    {
                        case 0:
                            if (end(text[tposition]))
                                stop = true;
                            else if (text[tposition] == '/')
                                state = 1;
                            else
                                return;
                            break;
                        case 1:
                            if (text[tposition] == '*')
                                state = 2;
                            else if (text[tposition] == '/')
                                state = 5;
                            break;
                        case 2:
                            if (text[tposition] == '*')
                                state = 3;
                            else if (text[tposition] != '*' && text[tposition] != '\0')
                                state = 2;
                            break;
                        case 3:
                            if (text[tposition] == '*')
                                state = 3;
                            else if (text[tposition] != '/' && text[tposition] != '\0')
                                state = 2;
                            else if (text[tposition] == '/')
                                state = 6;
                            break;
                        case 5:
                            if (text[tposition] == '\n')
                                stop = true;
                            break;
                        case 6:
                            stop = true;
                            break;
                    }

                    tposition++;
                }

                if (state == 6 || state == 5 || state == 0)
                    position = tposition;
            }
            while (stop);
        }

        private void SetTokenColumnAndLine(string text, Token token)
        {
            var line = 0;
            var column = 0;
            var position = token.Position;
            var firstOfLine = 0;

            for (int i = 0; i < text.Length; i++)
            {
                if (i == position)
                {
                    column = position - firstOfLine;
                    break;
                }

                if (text[i] == '\n')
                {
                    firstOfLine = i + 1;
                    line++;
                }
            }

            token.Line = line;
            token.Column = column;
        }

        #endregion

        #region Public Methods

        public IEnumerable<Token> GetTokens(string text)
        {
            int position = 0;
            Func<char, bool> space = c => c == ' ' || c == '\t' || c == '\n';

            while (position < text.Length)
            {
                while (position < text.Length && space(text[position]))
                    position++;

                if (position >= text.Length)
                    break;

                var lexeme = new LinkedList<char>();
                var startPosition = position;

                var token = _startState.Resolve(text, ref position, lexeme);
                token.Position = startPosition;
                this.SetTokenColumnAndLine(text, token);
                
                yield return token;
            }
        }

        #endregion
    }
}
